﻿using System;
using System.Collections.Generic;
using System.Linq.Expressions;
using System.Reflection;

namespace Glue.Internals
{
    internal partial class ObjectMemberFactory
    {
        private readonly INestedObjectCreator nestedObjectCreator;
        private readonly AccessorFactory accessorFactory = new AccessorFactory();
        public IRepresentBaseObject CreatePropertyRepresentingBaseObject(Type theTypeItRepresents)
        {
            return new MemberRepresentingBaseObject(theTypeItRepresents);
        }

        public ObjectMemberFactory(INestedObjectCreator nestedObjectCreator)
        {
            this.nestedObjectCreator = nestedObjectCreator;
        }

        public IObjectMember CreateObjectMember(PropertyInfo member)
        {
            var accessor = accessorFactory.Create(member);
            return GetObjectMember(accessor);
        }

        private IObjectMember CreateObjectMember(MethodInfo info)
        {
            return GetObjectMember(accessorFactory.Create(info));
        }

        private IObjectMember CreateObjectMember(FieldInfo fieldInfo)
        {
            return GetObjectMember(accessorFactory.Create(fieldInfo));
        }

        public IObjectMember CreateObjectMember(LambdaExpression lambda)
        {
            var lambdaDecomposer = new LambdaDecomposer(lambda);
            return lambdaDecomposer.IsNestedProperty ? 
                CreateNestedProperty(lambdaDecomposer.GetNestedParentObjectGetter(),GetObjectMember(lambdaDecomposer.GetAccessor())) 
                : GetObjectMember(lambdaDecomposer.GetAccessor());
        }

        public List<IObjectMember> GetObjectMembers(Type theTypeWithTheProperties)
        {
            return GetObjectMembers(theTypeWithTheProperties, false);
        }

        public List<IObjectMember> GetObjectMembers(Type theTypeWithTheProperties, bool includeFields)
        {
            var properties = new List<IObjectMember>();
            var propertyInfos = theTypeWithTheProperties.GetProperties();
            foreach (var propertyInfo in propertyInfos)
            {
                properties.Add(CreateObjectMember(propertyInfo));
            }
            if (includeFields)
                AddAllFields(theTypeWithTheProperties, properties);

            return properties;
        }

        private IObjectMember GetObjectMember(IObjectMemberAccessor accessor)
        {
            if (accessor.PropertyType.IsPrimitive())
                return new PrimitiveMember(accessor);
            
            if (accessor.PropertyType.IsArray)
                return new ArrayMember(accessor);
            if (accessor.PropertyType.IsOfInterfaceType(typeof(IDictionary<,>)))
                return new DictionaryMember(accessor);
            if (accessor.PropertyType.IsOfInterfaceType(typeof(IEnumerable<>)))
                return new EnumerableMember(accessor);
            
            return new NestedMember(accessor);
        }

        private IObjectMember CreateNestedProperty(CompiledExpression expression, IObjectMember propertyInfo)
        {
            return GetObjectMember(new NestedPropertyAccessor(expression,propertyInfo,nestedObjectCreator));
        }

        private void AddAllFields(Type theTypeWithTheProperties, List<IObjectMember> properties)
        {
            foreach (var field in theTypeWithTheProperties.GetFields())
            {
                properties.Add(CreateObjectMember(field));
            }
        }
    }
}
